package com.tiny.tinyweb.core.ldap.entry;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.beans.BeanUtils;

/**
 * 所有条目类的基类，该类实现了 {@link java.util.Map} 接口，可以灵活的保存条目中没有定义的属性。
 * @author wxiaobin
 */
public abstract class MapEntry implements Map<String, Object>, Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 存储条目的所有属性的资源库。
     */
    private final Map<String, Object> repository = new HashMap<String, Object>();

    /**
     * 获得组成 DN 的属性名列表，具体的条目类必须重写该方法，返回属于自己的 DN
     * 属性名列表。<b>必须不包含根 DN 中的属性名；列表项的顺序必须符合其在 DN 中出现的顺序。</b></br>
     * 如：<code>DN: cn=someone,ou=org,dc=domain,dc=com</code>，其中
     * <code>dc=domain,dc=com</code> 为根DN，属性中的 cn 和 ou 是 DN
     * 中的项目，那么返回的属性名列表必须是 <code>[cn, ou]</code>。
     * @return 组成 DN 的属性名列表
     */
    public abstract List<String> getDnKeys();

    /**
     * 获得该条目的所有属性的信息，每个属性信息都使用 {@link org.springframework.beans.BeanUtils}
     * 包装成 {@link java.beans.PropertyDescriptor} 之后被保存在 {@link java.util.LinkedHashMap} 中。
     * @return 获得该条目的所有属性的信息
     */
    public Map<String, PropertyDescriptor> getProperties() {
        // 定义一个 {@link java.util.LinkedHashMap} 对象来存储属性信息
        Map<String, PropertyDescriptor> properties =
                new LinkedHashMap<String, PropertyDescriptor>();
        // 循环所有的属性，则获得其信息
        PropertyDescriptor[] pds =
                BeanUtils.getPropertyDescriptors(this.getClass());
        for (PropertyDescriptor pd : pds) {
            if (!Arrays.asList("class", "properties", "empty", "dnKeys")
                    .contains(pd.getName())) {
                properties.put(pd.getName(), pd);
            }
        }
        return properties;
    }

    // <editor-fold defaultstate="collapsed" desc="override methods of object">
    /**
     * 比较两个条目是否相等。当两个条目具有相同的属性，并且所有属性的值相等，则他们是相等的。
     * @param obj 比较对象
     * @see {@link org.apache.commons.lang.builder.EqualsBuilder#append()},
     * {@link org.apache.commons.lang.builder.EqualsBuilder#isEquals()}
     */
    @Override
    public boolean equals(Object obj) {
        // 快速检查
        if (obj == null) {
            // 非空对象必定不等于空对象
            return false;
        } else if (this == obj) {
            // 指向同一对象，必定相等
            return true;
        } else if (this.getClass() != obj.getClass()) {
            // 不同类的对象，必定不相等
            return false;
        } else {
            // 比较两个条目对象是否相等
            MapEntry entry = (MapEntry) obj;
            if (this.size() != entry.size()) {
                // 属性数目不一致，必定不相等
                return false;
            }
            // 构造一个 EqualsBuilder 对象来判断目标对象是否相等
            EqualsBuilder eb = new EqualsBuilder();
            for (String key : this.keySet()) {
                eb.append(this.get(key), entry.get(key));
            }
            return eb.isEquals();
        }
    }

    /**
     * 获得该条目对象的散列码。根据所有属性散列。
     * @return 该条目对象的散列码
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int hashCode = 1;
        // 累加每一项的散列码
        for (String key : this.keySet()) {
            hashCode = prime * hashCode;
            Object value = this.get(key);
            if (value != null) {
                // 若某项目为空，则只计算基本值，否则累加该项目的散列码
                if (value instanceof Collection<?>) {
                    // 将集合对象包装成HashSet，然后取其散列码
                    hashCode += new HashSet<Object>((Collection<?>) value).hashCode();
                } else {
                    hashCode += hashCode + value.hashCode();
                }
            }
        }

        return hashCode;
    }

    /**
     * 将该对象转换为字符串返回。
     * @return 该对象的字符串表示
     * @see {@link org.apache.commons.lang.builder.ToStringBuilder#append()},
     * {@link org.apache.commons.lang.builder.ToStringBuilder#toString()}
     */
    @Override
    public String toString() {
        ToStringBuilder tsb = new ToStringBuilder(
                this, ToStringStyle.DEFAULT_STYLE);
        for (String key : this.keySet()) {
            tsb.append(key, this.get(key));
        }
        return tsb.toString();
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="interface methods of Map">
    @Override
	public void clear() {
        this.repository.clear();
    }

    @Override
	public boolean containsKey(Object key) {
        return this.repository.containsKey(key);
    }

    @Override
	public boolean containsValue(Object value) {
        return this.repository.containsValue(value);
    }

    @Override
	public Set<Entry<String, Object>> entrySet() {
        return this.repository.entrySet();
    }

    @Override
	public Object get(Object key) {
        return this.repository.get(key);
    }

    @Override
	public boolean isEmpty() {
        return this.repository.isEmpty();
    }

    @Override
	public Set<String> keySet() {
        return this.repository.keySet();
    }

    @Override
	public Object put(String key, Object value) {
        return this.repository.put(key, value);
    }

    @Override
	public void putAll(Map<? extends String, ? extends Object> m) {
        this.repository.putAll(m);
    }

    @Override
	public Object remove(Object key) {
        return this.repository.remove(key);
    }

    @Override
	public int size() {
        return this.repository.size();
    }

    @Override
	public Collection<Object> values() {
        return this.repository.values();
    }
    // </editor-fold>
}
